/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package phd.dupenois.changeidentifiers.algorithms.chihull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import uk.co.dupenois.geometry.Coordinate;
import uk.co.dupenois.geometry.Edge;

/**
 *
 * @author mpd209
 */
public class DelaunayTriangle implements Comparable<DelaunayTriangle>{
    private Coordinate vertex1;
    private Coordinate vertex2;
    private Coordinate vertex3;

    private DelaunayTriangle adjacentTriangle1;
    private DelaunayTriangle adjacentTriangle2;
    private DelaunayTriangle adjacentTriangle3;


    private Edge edge1to2;
    private Edge edge2to3;
    private Edge edge3to1;


    public DelaunayTriangle(Coordinate vertex1, Coordinate vertex2){
        this(vertex1, vertex2, null, true);
    }
    public DelaunayTriangle(Coordinate vertex1, Coordinate vertex2, boolean sort){
        this(vertex1, vertex2, null, sort);
    }
    public DelaunayTriangle(Coordinate vertex1, Coordinate vertex2, Coordinate vertex3){
        this(vertex1, vertex2, vertex3, true);
    }
    public DelaunayTriangle(Coordinate vertex1, Coordinate vertex2, Coordinate vertex3, boolean sort){
        if(sort){
            ArrayList<Coordinate> sorted = sortCoordinatesMinXMinY(vertex1, vertex2, vertex3);

            this.vertex1 = sorted.get(0);
            if(vertex3== null) {
                this.vertex2 = sorted.get(1);
                this.vertex3 = null;
            }else{
                //Of the two lines formed from vertex 1, one of them is left of the other,
                //this will be vertex 2
                Edge edgeto2 = new Edge(sorted.get(0), sorted.get(1));
                //If coordinate is left of line then it will be vertex 2, otherwise
                //it's vertex 3, if however they're in a line we need to deal with that too
                //
                int inRelation = edgeto2.coordinateInRelationToLine(vertex3);
                if(inRelation<0){
                    this.vertex2 = sorted.get(2);
                    this.vertex3 = sorted.get(1);
                }else if(inRelation>0){
                    this.vertex2 = sorted.get(1);
                    this.vertex3 = sorted.get(2);
                }else{
                    //need to be ordered to form a line, can leave them in current order,
                    //should do it
                    this.vertex2 = sorted.get(1);
                    this.vertex3 = sorted.get(2);
                }
            }
        }else{
            this.vertex1 = vertex1;
            this.vertex2 = vertex2;
            this.vertex3 = vertex3;
        }
        edge1to2 = new Edge(this.vertex1, this.vertex2);
        if(!isDegenerate()){
            edge2to3 = new Edge(this.vertex2, this.vertex3);
            if(!edge1to2.isOnLine(this.vertex3)){
                //Only need third edge if not all in a line
                edge3to1 = new Edge(this.vertex3, this.vertex1);
            }
        }
    }



    public boolean isDegenerate(){
        return (this.vertex3==null);
    }
    public boolean isAll3OnLine(){
        return (edge2to3!=null&&edge3to1==null);
    }
    public Edge sharedEdge(DelaunayTriangle other){
        Coordinate[] sharedVertices = sharedVertices(other);
        if(sharedVertices.length>1){
            List<Coordinate> dummyList = Arrays.asList(sharedVertices);
            if(dummyList.contains(this.vertex1)&&dummyList.contains(this.vertex2)){
                return getEdge1to2();
            }else if(dummyList.contains(this.vertex2)&&dummyList.contains(this.vertex3)){
                return getEdge2to3();
            }else{
                return getEdge3to1();
            }
        }
        return null;
    }

    public Coordinate[] sharedVertices(DelaunayTriangle other){
        ArrayList<Coordinate> vertices = new ArrayList<Coordinate>();
        Coordinate[] coordinates = getVertices();
        Coordinate[] otherCoordinates = other.getVertices();
        boolean match;
        Coordinate coordinate;
        for(Coordinate otherCoordinate : otherCoordinates){
            match = false;
            for(int i=0; !match && i <coordinates.length; i++){
                coordinate = coordinates[i];
                match = (otherCoordinate.equals(coordinate));
            }
            if(match)vertices.add(otherCoordinate);
        }
        return vertices.toArray(new Coordinate[vertices.size()]);
    }


    public Coordinate[] getVertices(){
        Coordinate[] coordinates;
        if(isDegenerate()){
            coordinates = new Coordinate[2];
            coordinates[0] = vertex1;
            coordinates[1] = vertex2;
        }else{
            coordinates = new Coordinate[3];
            coordinates[0] = vertex1;
            coordinates[1] = vertex2;
            coordinates[2] = vertex3;
        }
        return coordinates;

    }
    public boolean isTriangleAdjacent(DelaunayTriangle other){
        return (sharedEdge(other)!=null);
    }
    public int getTriangleOppositeVertex(DelaunayTriangle other){
        Edge line = sharedEdge(other);
        if(line == null) return -1;

        if(line.equals(getEdge1to2())) return 3;
        if(line.equals(getEdge2to3())) return 1;
        if(line.equals(getEdge3to1())) return 2;
        //Shouldn't get here but just in case sharedEdge is broken
        return -1;

//        Coordinate[] lineCoords = new Coordinate[]{line.getStart(), line.getFinish()};
//
//        /* If vertex 1 in line add 1, if vertex 2 add 2 and if 3 add 3
//         * If count = 3 then oppos. vertex is 3 (1+2)
//         * If count = 5 then oppos. vertex is 1 (2+3)
//         * If count = 4 then oppos. vertex is 2 (3+1)
//         */
//        int count = 0;
//        for(Coordinate coord : lineCoords ){
//            if(vertex1.equals(coord)) count += 1;
//            if(vertex2.equals(coord)) count += 2;
//            if(vertex3.equals(coord)) count += 3;
//        }
//        if(count == 3){
//            return 3;
//        }else if(count==5){
//            return 1;
//        }else if(count==4){
//            return 2;
//        }
//        //Shouldn't get here but just in case sharedEdge is broken
//        return -1;

    }

    public void addAdjacent(DelaunayTriangle other){
        int oppositeVertex = getTriangleOppositeVertex(other);
        if(oppositeVertex==1){
            setAdjacentTriangle1(other);
        }else if(oppositeVertex==2){
            setAdjacentTriangle2(other);
        }else if(oppositeVertex==3){
            setAdjacentTriangle3(other);
        }
        //Not adjacent if not 1,2 or 3
    }

    public void removeAdjacent(DelaunayTriangle other){
        int oppositeVertex = getTriangleOppositeVertex(other);
        if(oppositeVertex==1){
            setAdjacentTriangle1(null);
        }else if(oppositeVertex==2){
            setAdjacentTriangle2(null);
        }else if(oppositeVertex==3){
            setAdjacentTriangle3(null);
        }
    }

    /**
     * @return the vertex1
     */
    public Coordinate getVertex1() {
        return vertex1;
    }

    /**
     * @return the vertex2
     */
    public Coordinate getVertex2() {
        return vertex2;
    }

    /**
     * @return the vertex3
     */
    public Coordinate getVertex3() {
        return vertex3;
    }

    /**
     * Returns Adjacent Triangle opposite vertex 1 or first
     * adjacent triangle anti-clockwise from where triangle 1 would be
     * @return the adjacentTriangle1
     */
    public DelaunayTriangle getAdjacentTriangle1() {
        return getAdjacentTriangle(1);
    }

    /**
     * @param adjacentTriangle1
     */
    public void setAdjacentTriangle1(DelaunayTriangle adjacentTriangle1) {
        this.adjacentTriangle1 = adjacentTriangle1;
    }

    /**
     * Returns Adjacent Triangle opposite vertex 2 or first
     * adjacent triangle anti-clockwise from where triangle 2 would be
     * @return the adjacentTriangle2
     */
    public DelaunayTriangle getAdjacentTriangle2() {
        return getAdjacentTriangle(2);
    }

    /**
     * @param adjacentTriangle2
     */
    public void setAdjacentTriangle2(DelaunayTriangle adjacentTriangle2) {
        this.adjacentTriangle2 = adjacentTriangle2;
    }

    /**
     * Returns Adjacent Triangle opposite vertex 3 or first
     * adjacent triangle anti-clockwise from where triangle 3 would be
     * @return the adjacentTriangle3
     */
    public DelaunayTriangle getAdjacentTriangle3() {
        return getAdjacentTriangle(3);
    }

    /**
     * @param adjacentTriangle3
     */
    public void setAdjacentTriangle3(DelaunayTriangle adjacentTriangle3) {
        this.adjacentTriangle3 = adjacentTriangle3;
    }


    //Return first anti-clockwise existing triangle from one requested
    private DelaunayTriangle getAdjacentTriangle(int number){
        DelaunayTriangle adjacentTriangle  = null;
        for(int i =0; i< 3 && adjacentTriangle == null; i++){
            if(number == 1){
                if(this.adjacentTriangle1 != null){
                    adjacentTriangle = this.adjacentTriangle1;
                }else{
                    number = 3;
                }
            }else if(number==2){
                if(this.adjacentTriangle2 != null){
                    adjacentTriangle = this.adjacentTriangle2;
                }else{
                    number = 1;
                }
            }else if(number==3){
                if(this.adjacentTriangle3 != null){
                    adjacentTriangle = this.adjacentTriangle3;
                }else{
                    number = 2;
                }
            }

        }
        return adjacentTriangle;
    }




    private ArrayList<Coordinate> sortCoordinatesMinXMinY(Coordinate c1, Coordinate c2, Coordinate c3){
        ArrayList<Coordinate> coords = new ArrayList<Coordinate>();
        coords.add(c1);
        coords.add(c2);
        if(c3!=null)coords.add(c3);
        Collections.sort(coords, new Comparator<Coordinate>(){
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMinXMinY(arg1);
            }

        });
        return coords;
    }


   

    /**
     * @return the edge1to2
     */
    public Edge getEdge1to2() {
        return edge1to2;
    }

    /**
     * @return the edge2to3
     */
    public Edge getEdge2to3() {
        return edge2to3;
    }

    /**
     * @return the edge3to1
     */
    public Edge getEdge3to1() {
        return edge3to1;
    }

    public Edge[] getEdges(){
        Edge[] edges;
        if(isDegenerate()){
            edges = new Edge[1];
            edges[0] = getEdge1to2();
        }else if(isAll3OnLine()){
            edges = new Edge[2];
            edges[0] = getEdge1to2();
            edges[1] = getEdge2to3();
        }else{
            edges = new Edge[3];
            edges[0] = getEdge1to2();
            edges[1] = getEdge2to3();
            edges[2] = getEdge3to1();
        }
        return edges;
    }


    @Override
    public boolean equals(Object obj){
        if(!(obj instanceof DelaunayTriangle)) return false;
        if(obj == this) return true;
        DelaunayTriangle other = (DelaunayTriangle) obj;
        if(this.isDegenerate()&&other.isDegenerate()){
            if(other.getVertex1().equals(this.getVertex1())&&
                    other.getVertex2().equals(this.getVertex2())) return true;
            
        }else if(!this.isDegenerate()&&!other.isDegenerate()){
            if(other.getVertex1().equals(this.getVertex1())&&
                    other.getVertex2().equals(this.getVertex2())&&
                    other.getVertex3().equals(this.getVertex3())) return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 41 * hash + (this.vertex1 != null ? this.vertex1.hashCode() : 0);
        hash = 41 * hash + (this.vertex2 != null ? this.vertex2.hashCode() : 0);
        hash = 41 * hash + (this.vertex3 != null ? this.vertex3.hashCode() : 0);
        return hash;
    }


    @Override
    public String toString(){
        if(this.isDegenerate()){
            return "["+edge1to2.toString()+"]";
        }else if(this.isAll3OnLine()){
            return "["+edge1to2+"]-->["+edge2to3+"]";
        }else{
            return  "["+edge1to2+"]-->["+edge2to3+"]-->["+edge3to1+"]";
        }
    }

    public int compareTo(DelaunayTriangle arg0) {
        return this.compareByMinXMaxY(arg0);
//        return compareCoordinatesByMinXMaxY(this.vertex1, arg0.vertex1);
    }

    // <editor-fold desc="Utility comparator classes, use appropriate coordinate from triangles.">
    public final static ComparatorTrianglesByMinYMaxX
            comparatorTrianglesByMinYMaxX = new ComparatorTrianglesByMinYMaxX();
    public final static class ComparatorTrianglesByMinYMaxX implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMinYMaxX(arg1);
        }

    }
    public final static ComparatorTrianglesByMinYMinX
            comparatorTrianglesByMinYMinX = new ComparatorTrianglesByMinYMinX();
    public final static class ComparatorTrianglesByMinYMinX implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMinYMinX(arg1);
        }
    }
    public final static ComparatorTrianglesByMaxYMaxX
            comparatorTrianglesByMaxYMaxX = new ComparatorTrianglesByMaxYMaxX();
    public final static class ComparatorTrianglesByMaxYMaxX implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMaxYMaxX(arg1);
        }

    }
    public final static ComparatorTrianglesByMaxYMinX
            comparatorTrianglesByMaxYMinX = new ComparatorTrianglesByMaxYMinX();
    public static class ComparatorTrianglesByMaxYMinX implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMaxYMinX(arg1);
        }
    }

    public final static ComparatorTrianglesByMinXMaxY
            comparatorTrianglesByMinXMaxY = new ComparatorTrianglesByMinXMaxY();
    public final static class ComparatorTrianglesByMinXMaxY implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMinXMaxY(arg1);
        }

    }
    public final static ComparatorTrianglesByMinXMinY
            comparatorTrianglesByMinXMinY = new ComparatorTrianglesByMinXMinY();
    public final static class ComparatorTrianglesByMinXMinY implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMinXMinY(arg1);
        }
    }
    public final static ComparatorTrianglesByMaxXMaxY
            comparatorTrianglesByMaxXMaxY = new ComparatorTrianglesByMaxXMaxY();
    public final static class ComparatorTrianglesByMaxXMaxY implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMaxXMaxY(arg1);
        }

    }
    public final static ComparatorTrianglesByMaxXMinY
            comparatorTrianglesByMaxXMinY = new ComparatorTrianglesByMaxXMinY();
    public final static class ComparatorTrianglesByMaxXMinY implements Comparator<DelaunayTriangle>{
        public int compare(DelaunayTriangle arg0, DelaunayTriangle arg1) {
            return arg0.compareByMaxXMinY(arg1);
        }
    }

    // </editor-fold>

    // <editor-fold desc="Utility comparator classes uses appropriate coordinate to compare triangles.">
    public int compareByMinXMaxY(DelaunayTriangle arg1){
        return this.getVertexMinXMaxY().compareByMinXMaxY(arg1.getVertexMinXMaxY());
    }
    public int compareByMinXMinY(DelaunayTriangle arg1){
        return this.getVertexMinXMinY().compareByMinXMinY(arg1.getVertexMinXMinY());
    }

    public int compareByMaxXMaxY(DelaunayTriangle arg1){
        return this.getVertexMaxXMaxY().compareByMaxXMaxY(arg1.getVertexMaxXMaxY());
    }
    public int compareByMaxXMinY(DelaunayTriangle arg1){
        return this.getVertexMaxXMinY().compareByMaxXMinY(arg1.getVertexMaxXMinY());
    }
    public int compareByMinYMaxX(DelaunayTriangle arg1){
        return this.getVertexMinYMaxX().compareByMinYMaxX(arg1.getVertexMinYMaxX());
    }

    public int compareByMinYMinX(DelaunayTriangle arg1){
        return this.getVertexMinYMinX().compareByMinYMinX(arg1.getVertexMinYMinX());
    }

    public int compareByMaxYMaxX(DelaunayTriangle arg1){
        return this.getVertexMaxYMaxX().compareByMaxYMaxX(arg1.getVertexMaxYMaxX());
    }
    public int compareByMaxYMinX(DelaunayTriangle arg1){
        return this.getVertexMaxYMinX().compareByMaxYMinX(arg1.getVertexMaxYMinX());
    }
    // </editor-fold>

    // <editor-fold desc="Utility comparator methods based on coordinates.">
    public Coordinate getVertexMinXMinY(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMinXMinY(arg1);
            }
        });
        return vertices[0];
    }
    public Coordinate getVertexMinXMaxY(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMinXMaxY(arg1);
            }
        });
        return vertices[0];
    }
    public Coordinate getVertexMaxXMinY(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMaxXMinY(arg1);
            }
        });
        return vertices[0];
    }

    public Coordinate getVertexMaxXMaxY(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMaxXMaxY(arg1);
            }
        });
        return vertices[0];
    }
     public Coordinate getVertexMinYMinX(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMinYMinX(arg1);
            }
        });
        return vertices[0];
    }
    public Coordinate getVertexMinYMaxX(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMinYMaxX(arg1);
            }
        });
        return vertices[0];
    }
    public Coordinate getVertexMaxYMinX(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMaxYMinX(arg1);
            }
        });
        return vertices[0];
    }

    public Coordinate getVertexMaxYMaxX(){
        Coordinate[] vertices = getVertices();
        Arrays.sort(vertices, new Comparator<Coordinate>() {
            public int compare(Coordinate arg0, Coordinate arg1) {
                return arg0.compareByMaxYMaxX(arg1);
            }
        });
        return vertices[0];
    }
    // </editor-fold>

}
